﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Cinchoo.Core.ETL;
using ChoWorkflowServer.Model;
using Cinchoo.Core;

namespace ChoWorkflowServer
{
    public class ChoMonthlyCalendarIntervalTrigger : ChoETLCalendarIntervalTrigger
    {
        private ChoTasksDataSet.CHO_TASK_SCHEDULESRow _scheduleRow;
        private bool _firstTime = true;
        private DateTimeOffset? _origAfterTime;
        private IEnumerator<DateTimeOffset?> _nextFireTimeUtcDates = null; // new List<DateTimeOffset?>();

        public ChoMonthlyCalendarIntervalTrigger(string name)
            : this(name, DateTimeOffset.UtcNow, null)
        {
        }

        /// <summary>
        /// Create a <see cref="ICalendarIntervalTrigger" /> that will occur at the given time,
        /// and repeat at the the given interval until the given end time.
        /// </summary>
        /// <param name="name">Name for the trigger instance.</param>
        /// <param name="startTimeUtc">A <see cref="DateTimeOffset" /> set to the time for the <see cref="ITrigger" /> to fire.</param>
        /// <param name="endTimeUtc">A <see cref="DateTimeOffset" /> set to the time for the <see cref="ITrigger" /> to quit repeat firing.</param>
        /// <param name="intervalUnit">The repeat interval unit (minutes, days, months, etc).</param>
        /// <param name="repeatInterval">The number of milliseconds to pause between the repeat firing.</param>
        public ChoMonthlyCalendarIntervalTrigger(string name, DateTimeOffset startTimeUtc,
                                           DateTimeOffset? endTimeUtc)
            : base(name, startTimeUtc, endTimeUtc, ChoETLIntervalUnit.Month, 1)
        {
        }

        public override DateTimeOffset? ComputeFirstFireTime(IChoETLCalendar calendar)
        {
            ComputeNextFireDates(base.ComputeFirstFireTime(calendar));

            DateTimeOffset? nextFireTime = null;
            if (_nextFireTimeUtcDates != null && _nextFireTimeUtcDates.MoveNext())
                nextFireTime = _nextFireTimeUtcDates.Current;

            return nextFireTime;
        }

        public override DateTimeOffset? GetFireTimeAfter(DateTimeOffset? afterTime)
        {
            if (_nextFireTimeUtcDates == null) return null;

            if (_firstTime)
            {
                _firstTime = false;
                _origAfterTime = afterTime;
            }

            DateTimeOffset? nextFireTime = null;
            if (!_nextFireTimeUtcDates.MoveNext())
            {
                int everyNoOfWeeks = _scheduleRow != null ? _scheduleRow.WEEKLY_EVERY_WEEKS : 1;
                if (everyNoOfWeeks <= 0)
                    throw new ApplicationException("Invalid EveryNoOfWeeks found.");

                for (int counter = 0; counter < everyNoOfWeeks; counter++)
                {
                    _origAfterTime = nextFireTime = base.GetFireTimeAfter(_origAfterTime);
                    if (nextFireTime == null)
                        break;
                }

                ComputeNextFireDates(nextFireTime);
            }
            else
                nextFireTime = _nextFireTimeUtcDates.Current;

            return nextFireTime;
        }

        private void ComputeNextFireDates(DateTimeOffset? nextFireTime)
        {
            if (nextFireTime == null)
                _nextFireTimeUtcDates = null;
            else
            {
                if (_scheduleRow.MONTHLY_DAY_OF_MONTH)
                {
                    DateTimeOffset lNextFireDateTime = nextFireTime.Value.DateTime;
                    int dayOfMonth = _scheduleRow.MONTHLY_EVERY_DAY_OF_MONTH;
                    if (dayOfMonth < 1 || dayOfMonth > 31)
                        throw new ApplicationException("Invalid day of month found.");

                    try
                    {
                        lNextFireDateTime = new DateTimeOffset(nextFireTime.Value.Year, nextFireTime.Value.Month, dayOfMonth,
                            nextFireTime.Value.Hour, nextFireTime.Value.Minute, nextFireTime.Value.Second, nextFireTime.Value.Millisecond, nextFireTime.Value.Offset);
                        if ((EndTimeUtc == null || lNextFireDateTime <= EndTimeUtc.Value))
                            _nextFireTimeUtcDates = ChoCalendarIntervalUtil.ComputeRepeatFireDates(_scheduleRow, lNextFireDateTime).GetEnumerator();
                        //_nextFireTimeUtcDates.AddRange(ChoCalendarIntervalUtil.ComputeRepeatFireDates(_scheduleRow, lNextFireDateTime));
                    }
                    catch
                    {
                    }
                }
                else
                {
                    try
                    {
                        if (_scheduleRow.IsMONTHLY_SELECTED_MONTHSNull())
                            return;

                        DayOfWeek dayOfWeek = (DayOfWeek)_scheduleRow.MONTHLY_EVERY_WEEKDAY_OF_MONTH;
                        ChoWeekOfMonth weekOfMonth = (ChoWeekOfMonth)_scheduleRow.MONTHLY_EVERY_WEEK_OF_MONTH;

                        string monthlySelectedMonths = _scheduleRow.MONTHLY_SELECTED_MONTHS;
                        if (monthlySelectedMonths.IsNullOrWhiteSpace() ||
                            monthlySelectedMonths.Length != 12)
                            return;

                        if (monthlySelectedMonths[0] == '1') //JAN
                            DiscoverDates(1, nextFireTime, dayOfWeek, weekOfMonth);
                        if (monthlySelectedMonths[0] == '1') //FEB
                            DiscoverDates(2, nextFireTime, dayOfWeek, weekOfMonth);
                        if (monthlySelectedMonths[0] == '1') //MAR
                            DiscoverDates(3, nextFireTime, dayOfWeek, weekOfMonth);
                        if (monthlySelectedMonths[0] == '1') //APR
                            DiscoverDates(4, nextFireTime, dayOfWeek, weekOfMonth);
                        if (monthlySelectedMonths[0] == '1') //MAY
                            DiscoverDates(5, nextFireTime, dayOfWeek, weekOfMonth);
                        if (monthlySelectedMonths[0] == '1') //JUN
                            DiscoverDates(6, nextFireTime, dayOfWeek, weekOfMonth);
                        if (monthlySelectedMonths[0] == '1') //JUL
                            DiscoverDates(7, nextFireTime, dayOfWeek, weekOfMonth);
                        if (monthlySelectedMonths[0] == '1') //AUG
                            DiscoverDates(8, nextFireTime, dayOfWeek, weekOfMonth);
                        if (monthlySelectedMonths[0] == '1') //SEP
                            DiscoverDates(9, nextFireTime, dayOfWeek, weekOfMonth);
                        if (monthlySelectedMonths[0] == '1') //OCT
                            DiscoverDates(10, nextFireTime, dayOfWeek, weekOfMonth);
                        if (monthlySelectedMonths[0] == '1') //NOV
                            DiscoverDates(11, nextFireTime, dayOfWeek, weekOfMonth);
                        if (monthlySelectedMonths[0] == '1') //DEC
                            DiscoverDates(12, nextFireTime, dayOfWeek, weekOfMonth);
                    }
                    catch
                    {
                    }
                }
            }
        }

        private void DiscoverDates(int month, DateTimeOffset? nextFireTime, DayOfWeek dayOfWeek, ChoWeekOfMonth weekOfMonth)
        {
            DateTimeOffset lNextFireDateTime = nextFireTime.Value.DateTime;
            List<DateTime> dates = new List<DateTime>(new DateTime(nextFireTime.Value.Year, month, nextFireTime.Value.Day,
                nextFireTime.Value.Hour, nextFireTime.Value.Minute, nextFireTime.Value.Second, nextFireTime.Value.Millisecond).GetWeekDatesOfMonth(dayOfWeek));

            if (weekOfMonth == ChoWeekOfMonth.First)
            {
                if (dates.Count >= 1)
                    lNextFireDateTime = new DateTimeOffset(dates[0].Year, dates[0].Month, dates[0].Day,
                        nextFireTime.Value.Hour, nextFireTime.Value.Minute, nextFireTime.Value.Second, nextFireTime.Value.Millisecond, nextFireTime.Value.Offset);
            }
            else if (weekOfMonth == ChoWeekOfMonth.Second)
            {
                if (dates.Count >= 2)
                    lNextFireDateTime = new DateTimeOffset(dates[1].Year, dates[1].Month, dates[1].Day,
                        nextFireTime.Value.Hour, nextFireTime.Value.Minute, nextFireTime.Value.Second, nextFireTime.Value.Millisecond, nextFireTime.Value.Offset);
            }
            else if (weekOfMonth == ChoWeekOfMonth.Third)
            {
                if (dates.Count >= 3)
                    lNextFireDateTime = new DateTimeOffset(dates[2].Year, dates[2].Month, dates[2].Day,
                        nextFireTime.Value.Hour, nextFireTime.Value.Minute, nextFireTime.Value.Second, nextFireTime.Value.Millisecond, nextFireTime.Value.Offset);
            }
            else if (weekOfMonth == ChoWeekOfMonth.Fourth)
            {
                if (dates.Count >= 4)
                    lNextFireDateTime = new DateTimeOffset(dates[3].Year, dates[3].Month, dates[3].Day,
                        nextFireTime.Value.Hour, nextFireTime.Value.Minute, nextFireTime.Value.Second, nextFireTime.Value.Millisecond, nextFireTime.Value.Offset);
            }
            else if (weekOfMonth == ChoWeekOfMonth.Last)
            {
                if (dates.Count >= 3)
                    lNextFireDateTime = new DateTimeOffset(dates[dates.Count - 1].Year, dates[dates.Count - 1].Month, dates[dates.Count - 1].Day,
                        nextFireTime.Value.Hour, nextFireTime.Value.Minute, nextFireTime.Value.Second, nextFireTime.Value.Millisecond, nextFireTime.Value.Offset);
            }

            if ((EndTimeUtc == null || lNextFireDateTime <= EndTimeUtc.Value))
                            _nextFireTimeUtcDates = ChoCalendarIntervalUtil.ComputeRepeatFireDates(_scheduleRow, lNextFireDateTime).GetEnumerator();
            //_nextFireTimeUtcDates.AddRange(ChoCalendarIntervalUtil.ComputeRepeatFireDates(_scheduleRow, lNextFireDateTime));
        }

        public override object Tag
        {
            get
            {
                return _scheduleRow;
            }
            set
            {
                _scheduleRow = value as ChoTasksDataSet.CHO_TASK_SCHEDULESRow;
            }
        }
    }
}
